﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Collections;
using System.Diagnostics;
using System.Threading;
using SystemTimeZoneInfo = System.TimeZoneInfo;

namespace King.Extensions {
    public static partial class Extensions {
        public static DateTime RoundDownToHour(this DateTime dateTime) {
            var result = dateTime
                .AddMilliseconds(-dateTime.Millisecond)
                .AddSeconds(-dateTime.Second)
                .AddMinutes(-dateTime.Minute);

            return result;
        }
        public static DateTime RoundUpToHour(this DateTime dateTime) {

            if (dateTime.Millisecond == 0 &&
                dateTime.Second == 0 &&
                dateTime.Minute == 0)
                return dateTime;

            // round up to the nearest hour
            var result = dateTime
                .AddMilliseconds(-dateTime.Millisecond)
                .AddSeconds(-dateTime.Second)
                .AddMinutes(-dateTime.Minute)
                .AddHours(1);

            return result;
        }
        public static bool IsMidnight(this DateTime dateTime) {
            return dateTime.Date.Equals(dateTime);
        }
    }

    public struct DateTimeZone {

        public static implicit operator DateTimeZone(DateTime dateTime) {
            if (dateTime.Kind == DateTimeKind.Local)
                dateTime = dateTime.ToUniversalTime();

            return new DateTimeZone(
                dateTime.Year,
                dateTime.Month,
                dateTime.Day,
                dateTime.Hour,
                dateTime.Minute,
                null);
        }
        public static implicit operator DateTime(DateTimeZone dateTimeZone) {
            return dateTimeZone.ToUtcDateTime();
        }

        private DateTimeOffset m_dateTimeOffset;
        private TimeZoneInfo m_timeZoneInfo;

        public DateTimeZone(DateTime dateTime, TimeZoneInfo timeZoneInfo)
            : this(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, timeZoneInfo) {

            if (dateTime.Kind != DateTimeKind.Utc)
                throw new ArgumentException();
        }
        public DateTimeZone(int year, int month, TimeZoneInfo timeZoneInfo)
            : this(year, month, 1, timeZoneInfo) {
        }
        public DateTimeZone(int year, int month, int day, TimeZoneInfo timeZoneInfo)
            : this(year, month, 1, 1, 0, timeZoneInfo) {
        }
        public DateTimeZone(int year, int month, int day, int hour, int minute, TimeZoneInfo timeZoneInfo) {
            m_timeZoneInfo = timeZoneInfo;

            if (timeZoneInfo == null)
                timeZoneInfo = TimeZoneInfo.Utc;

            var dateTimeGmt = new DateTime(year, month, day, hour, minute, 0, DateTimeKind.Unspecified);
            var dateTime = TimeZoneInfo.ConvertTimeFromUtc(dateTimeGmt, timeZoneInfo);
            var offset = dateTime - dateTimeGmt;

            m_dateTimeOffset = new DateTimeOffset(dateTimeGmt, offset);
        }

        public TimeSpan Offset { 
            get { return m_dateTimeOffset.Offset; } 
        }
        public TimeZoneInfo TimeZoneInfo {
            get { return m_timeZoneInfo; } 
        }
        public DateTimeOffset TimeZoneOffset {
            get { return m_dateTimeOffset; }
        }

        public int Year { 
            get { return m_dateTimeOffset.Year; } 
        }
        public int Month { 
            get { return m_dateTimeOffset.Month; } 
        }
        public int Day {
            get { return m_dateTimeOffset.Day; } 
        }
        public int Hour { 
            get { return m_dateTimeOffset.Hour; } 
        }
        public int Minute { 
            get { return m_dateTimeOffset.Minute; } 
        }

        public DateTimeZone AddMonths(int months) {
            throw new NotImplementedException();
        }
        public DateTimeZone AddDays(int days) {
            throw new NotImplementedException();
        }

        public DateTime ToUtcDateTime() {
            return m_dateTimeOffset.ToUniversalTime().DateTime;
        }

        public override string ToString() {
            return m_dateTimeOffset.ToString();
        }
        public override bool Equals(object obj) {
            if (!(obj is DateTimeZone))
                return false;
            return m_dateTimeOffset.Equals(((DateTimeZone)obj).m_dateTimeOffset);
        }
        public override int GetHashCode() {
            return m_dateTimeOffset.GetHashCode();
        }
    }
}